package ourvod.rtp;

import java.awt.Dimension;
import java.io.IOException;

import javax.media.Codec;
import javax.media.Control;
import javax.media.Controller;
import javax.media.ControllerClosedEvent;
import javax.media.ControllerEvent;
import javax.media.ControllerListener;
import javax.media.Format;
import javax.media.Manager;
import javax.media.MediaLocator;
import javax.media.NoProcessorException;
import javax.media.Owned;
import javax.media.Player;
import javax.media.Processor;
import javax.media.control.QualityControl;
import javax.media.control.TrackControl;
import javax.media.format.VideoFormat;
import javax.media.protocol.ContentDescriptor;
import javax.media.protocol.DataSource;
import javax.media.protocol.PushBufferDataSource;
import javax.media.protocol.PushBufferStream;
import javax.media.rtp.RTPManager;
import javax.media.rtp.SendStream;

import net.jxta.protocol.PipeAdvertisement;

/**
 *
 * @author cuizhenfu@gmail.com
 */
public class RTPJxtaMulticastSender {
    // 媒体定位
    // Input MediaLocator, can be a file or http or capture source
    private MediaLocator locator = null;
    // 处理器
    private Processor processor = null;
    // RTM管理器
    private RTPManager rtpMgrs[];
    // 输出的数据源
    private DataSource dataOutput = null;
    
    private PipeAdvertisement dataPipeAdv;
    private PipeAdvertisement ctrlPipeAdv;

    public RTPJxtaMulticastSender(MediaLocator locator, PipeAdvertisement dataPipeAdv, PipeAdvertisement ctrlPipeAdv) {
        this.locator = locator;
        this.dataPipeAdv = dataPipeAdv;
        this.ctrlPipeAdv = ctrlPipeAdv;
        
        if(this.locator == null){
        	System.out.println("locator is null!");
        }
        else if(this.dataPipeAdv == null){
        	System.out.println("dataPipeAdv is null");
        }
        else if(this.ctrlPipeAdv == null){
        	System.out.println("ctrlPipeAdv is null");
        }
        System.out.println(locator.toString());
    }

    /**
     * 开始传输
     * Starts the transmission. Returns null if transmission started ok.
     * Otherwise it returns a string with the reason why the setup failed.
     */
    public synchronized String start() {
        String result;

        // 创建一个处理器
        // Create a processor for the specified media locator
        // and program it to output JPEG/RTP
        result = createProcessor();
        if (result == null) { /////////////////是不是null？？？？
            return result;
        }

        // 创建RTP会话
        // Create an RTP session to transmit the output of the
        // processor to the specified IP address and port no.
        result = createTransmitter();
        if (result != null) {
            processor.close();
            processor = null;
            return result;
        }

        // 让处理器开始传输
        // Start the transmission
        processor.start();

        return null;
    }

    /**
     * 为指定的媒体定位器产生一个处理器，返回null，则相安无事
     */
    private String createProcessor() {
        if (locator == null) {
            return "Locator is null";
        }

        // 为定义的MediaLocator定位并实例化一个适当的数据源
        DataSource ds;
        try {
            ds = Manager.createDataSource(locator);
        } catch (Exception ex) {
            return "Couldn't create DataSource";
        }

        // 通过数据源来产生一个处理器
        // Try to create a processor to handle the input media locator
        try {
            processor = Manager.createProcessor(ds);
        } catch (IOException ex) {
            return "IOException creating processor";
        } catch (NoProcessorException ex) {
            return "Couldn't create processor";
        }

        // 等待处理器配置好
        // Wait for it to configure
        boolean result = waitForState(processor, Processor.Configured);
        if (result == false) {
            return "Couldn't configure processor";
        }

        // 为媒体流中的每一个轨迹获取一个控制器
        // Get the tracks from the processor
        TrackControl[] tracks = processor.getTrackControls();

        // 确保至少有一个可用的轨迹
        // Do we have at least one track?
        if (tracks == null || tracks.length < 1) {
            return "Couldn't find tracks in processor";
        }

        // Set the output content descriptor to RAW_RTP
        // This will limit the supported formats reported from
        // Track.getSupportedFormats to only valid RTP formats.
        // 当设定JMF处理器的内容描述为"RAW_RTP"时，将限定每个轨迹支持的
        // 格式仅为合法的RTP格式。
        ContentDescriptor cd = new ContentDescriptor(ContentDescriptor.RAW_RTP);
        processor.setContentDescriptor(cd);

        Format supported[];
        Format chosen = null;
        boolean atLeastOneTrack = false;

        // Program the tracks.
        for (int i = 0; i < tracks.length; i++) {
            Format format = tracks[i].getFormat();
            if (tracks[i].isEnabled()) {
                supported = tracks[i].getSupportedFormats();

                // We've set the output content to the RAW_RTP.
                // So all the supported formats should work with RTP.
                // We'll just pick the first one.
                if (supported.length > 0) {
                    if (supported[0] instanceof VideoFormat) {
                        // For video formats, we should double check the
                        // sizes since not all formats work in all sizes.
                        chosen = checkForVideoSizes(tracks[i].getFormat(), supported[0]);
                    } else {
                        chosen = supported[0];
                    }
                    tracks[i].setFormat(chosen);
                    System.err.println("Track " + i + " is set to transmit as:");
                    System.err.println("  " + chosen);
                    atLeastOneTrack = true;
                } else {
                    tracks[i].setEnabled(false);
                }
            } else {
                tracks[i].setEnabled(false);
            }
        }

        // 如果每个轨迹都不存在合适的格式
        if (!atLeastOneTrack) {
            return "Couldn't set any of the tracks to a valid RTP format";
        }

        // Realize the processor. This will internally create a flow
        // graph and attempt to create an output datasource for JPEG/RTP
        // audio frames.
        // 等待处理器实现 
        result = waitForState(processor, Controller.Realized);
        if (result == false) {
            return "Couldn't realize processor";
        }

        // Set the JPEG quality to .5.
        setJPEGQuality(processor, 0.1f);

        // Get the output data source of the processor
        // 从处理器获取输出的数据源
        dataOutput = processor.getDataOutput();

        return null;
    }

    /**
     * Use the RTPManager API to create sessions for each media 
     * track of the processor.
     * 产生RTP会话传输，返回null，则相安无事
     */
    private String createTransmitter() {

        // Cheated.  Should have checked the type.
        // 将数据源转化为"Push"(推)数据源并获取"Push"数据流
    	PushBufferDataSource pbds = (PushBufferDataSource) dataOutput;
        PushBufferStream pbss[] = pbds.getStreams();
        
        // 为每个轨迹产生一个RTP会话管理器
        rtpMgrs = new RTPManager[pbss.length];
        for (int i = 0; i < pbss.length; i++) {
            try {
                rtpMgrs[i] = RTPManager.newInstance();
                rtpMgrs[i].initialize(new RTPJxtaMulticastSocketAdapter(dataPipeAdv, ctrlPipeAdv));

                SendStream sendStream = rtpMgrs[i].createSendStream(dataOutput, i);
                sendStream.start();
            } catch (Exception e) {
                return e.getMessage();
            }
        }

        return null;
    }

    /**
     * For JPEG and H263, we know that they only work for particular
     * sizes.  So we'll perform extra checking here to make sure they
     * are of the right sizes.
     * 检查视频图像的尺寸
     */
    Format checkForVideoSizes(Format original, Format supported) {

        int width, height;

        // 获取视频图像的尺寸  
        Dimension size = ((VideoFormat) original).getSize();
        Format jpegFmt = new Format(VideoFormat.JPEG_RTP);
        Format h263Fmt = new Format(VideoFormat.H263_RTP);

        if (supported.matches(jpegFmt)) {
            // For JPEG, make sure width and height are divisible by 8.
            // 调整宽度
            width = (size.width % 8 == 0 ? size.width
                    : (int) (size.width / 8) * 8);
            // 调整高度
            height = (size.height % 8 == 0 ? size.height
                    : (int) (size.height / 8) * 8);
        } else if (supported.matches(h263Fmt)) {
            // For H.263, we only support some specific sizes.
            if (size.width < 128) {
                width = 128;
                height = 96;
            } else if (size.width < 176) {
                width = 176;
                height = 144;
            } else {
                width = 352;
                height = 288;
            }
        } else {
            // We don't know this particular format.  We'll just
            // leave it alone then.
            // 对其他格式不予处理
        	return supported;
        }

        // 返回调整后的视频格式
        return (new VideoFormat(null,
                new Dimension(width, height),
                Format.NOT_SPECIFIED,
                null,
                Format.NOT_SPECIFIED)).intersects(supported);
    }

    /**
     * Setting the encoding quality to the specified value on the JPEG encoder.
     * 0.5 is a good default.
     */
    void setJPEGQuality(Player p, float val) {

        Control cs[] = p.getControls();
        QualityControl qc = null;
        VideoFormat jpegFmt = new VideoFormat(VideoFormat.JPEG);

        // Loop through the controls to find the Quality control for
        // the JPEG encoder.
        for (int i = 0; i < cs.length; i++) {

            if (cs[i] instanceof QualityControl
                    && cs[i] instanceof Owned) {
                Object owner = ((Owned) cs[i]).getOwner();

                // Check to see if the owner is a Codec.
                // Then check for the output format.
                if (owner instanceof Codec) {
                    Format fmts[] = ((Codec) owner).getSupportedOutputFormats(null);
                    for (int j = 0; j < fmts.length; j++) {
                        if (fmts[j].matches(jpegFmt)) {
                            qc = (QualityControl) cs[i];
                            qc.setQuality(val);
                            System.err.println("- Setting quality to "
                                    + val + " on " + qc);
                            break;
                        }
                    }
                }
                if (qc != null) {
                    break;
                }
            }
        }
    }

    /**
     * Stops the transmission if already started
     * 停止传输
     */
    public void stop() {
        synchronized (this) {
            if (processor != null) {
                // 停止和关闭处理器
                processor.stop();
                processor.close();
                processor = null;

                // 删除所有RTP管理器
                for (int i = 0; i < rtpMgrs.length; i++) {
                    rtpMgrs[i].removeTargets("Session ended.");
                    rtpMgrs[i].dispose();
                }
            }
        }
    }
    /****************************************************************
     * Convenience methods to handle processor's state changes.
     ****************************************************************/
    // 以下两个变量为对处理器状态改变的处理服务
    private Integer stateLock = new Integer(0); // 状态锁变量
    private boolean failed = false;             // 是否失败的状态标志
    // 获取状态锁
    Integer getStateLock() {
        return stateLock;
    }

    // 设置失败标志
    void setFailed() {
        failed = true;
    }

    // 等待处理器达到相应的状态
    private synchronized boolean waitForState(Processor p, int state) {
        // 为处理器加上状态监听
        p.addControllerListener(new StateListener());
        failed = false;

        // Call the required method on the processor
        // 配置和实现处理器
        if (state == Processor.Configured) {
            p.configure();
        } else if (state == Processor.Realized) {
            p.realize();
        }

        // Wait until we get an event that confirms the
        // success of the method, or a failure event.
        // See StateListener inner class
        // 一直等待，直到成功达到所需状态，或失败
        while (p.getState() < state && !failed) {
            synchronized (getStateLock()) {
                try {
                    getStateLock().wait();  // 等待
                } catch (InterruptedException ie) {
                    return false;
                }
            }
        }

        if (failed) {
            return false;
        } else {
            return true;
        }
    }

    /****************************************************************
     * Inner Classes
     ****************************************************************/
    // 内部监听类：处理器的状态监听器
    class StateListener implements ControllerListener {

        @Override
        public void controllerUpdate(ControllerEvent ce) {

            // If there was an error during configure or
            // realize, the processor will be closed
            if (ce instanceof ControllerClosedEvent) {  // 控制器关闭
            	setFailed();
            }

            // All controller events, send a notification
            // to the waiting thread in waitForState method.
            if (ce instanceof ControllerEvent) {    // 对于所有的控制器事件
                synchronized (getStateLock()) {
                    // 通知在waitForState()方法中等待的线程
                    getStateLock().notifyAll();
                }
            }
        }
    }
}